package edu.fib.upc.erdapfel;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.mapsforge.android.maps.MapView;
import org.mapsforge.android.maps.Projection;
import org.mapsforge.android.maps.overlay.Overlay;
import org.mapsforge.android.maps.overlay.OverlayWay;
import org.mapsforge.core.model.GeoPoint;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.PorterDuff.Mode;
import android.view.MotionEvent;

public class PolygonDrawOverlay extends Overlay {

	/* constant for defining the time duration between the click that can be considered as double-tap */
	final static int MAX_DURATION = 300;

	private long duration;
	private int clickCount = 0;
	private long startTime;

	Paint defaultPaintFill;
	Paint defaultPaintOutline;
	Paint defaultPaintVertex;
	Paint defaultPaintText;

	List<Point> points;

	Projection projection;

	private int selectedPoint = Integer.MAX_VALUE;	

	public PolygonDrawOverlay() {		

		this.defaultPaintFill = initDefaultPaintFill();
		this.defaultPaintOutline = initDefaultPaintOutline();
		this.defaultPaintVertex = initDefaultPaintVertex();
		this.defaultPaintText = initDefaultPaintText();

		this.points = new ArrayList<Point>();
		this.projection = null;		
	}

	private Paint initDefaultPaintText() {
		Paint mPaint = new Paint();
		mPaint.setDither(true);
		mPaint.setAntiAlias(true);
		mPaint.setColor(Color.parseColor("#ffA4C639"));
		mPaint.setStyle(Paint.Style.STROKE);
		mPaint.setStrokeJoin(Paint.Join.MITER);
		mPaint.setStrokeCap(Paint.Cap.ROUND);
		mPaint.setStrokeWidth(1.5f);					
		mPaint.setTextAlign(Align.CENTER);
		mPaint.setTextSize(12);		
		mPaint.setShadowLayer(mPaint.getStrokeWidth()/2, mPaint.getStrokeWidth()/4, mPaint.getStrokeWidth()/4, Color.BLACK);

		return mPaint;
	}

	public PolygonDrawOverlay(Projection projection) {		

		this.defaultPaintFill = initDefaultPaintFill();
		this.defaultPaintOutline = initDefaultPaintOutline();

		this.points = new ArrayList<Point>();
		this.projection = projection;
	}

	private static Paint initDefaultPaintOutline() {
		Paint mPaint;

		mPaint = new Paint();
		mPaint.setDither(true);
		mPaint.setAntiAlias(true);
		mPaint.setColor(Color.parseColor("#ffA4C639"));
		mPaint.setStyle(Paint.Style.STROKE);
		mPaint.setStrokeJoin(Paint.Join.ROUND);
		mPaint.setStrokeCap(Paint.Cap.ROUND);
		mPaint.setStrokeWidth(5);
		mPaint.setPathEffect(new DashPathEffect(new float[] {10,20}, 0));
		mPaint.setShadowLayer(mPaint.getStrokeWidth()/2, mPaint.getStrokeWidth()/4, mPaint.getStrokeWidth()/4, Color.BLACK);

		return mPaint;
	}

	private Paint initDefaultPaintVertex() {
		Paint mPaint = initDefaultPaintOutline();		
		mPaint.setStrokeWidth(20); // TODO: this shall be dependent on screen size?
		mPaint.setShadowLayer(mPaint.getStrokeWidth()/2, mPaint.getStrokeWidth()/4, mPaint.getStrokeWidth()/4, Color.BLACK);
		return mPaint;
	}

	private static Paint initDefaultPaintFill() {

		return new Paint();
	}

	public OverlayWay getWay() {		
		if( this.points == null || this.points.isEmpty())
				return null;
		OverlayWay way = new OverlayWay();	

		GeoPoint[][] nodes = new GeoPoint[][] {{}};				

		// not allowing multi-polygons, so only one dimension		
		nodes[0] = new GeoPoint[this.points.size()];
		int i = 0;
		while(i< nodes[0].length) {
			nodes[0][i] = this.projection.fromPixels(this.points.get(i).x, this.points.get(i).y);
			i++;
		}				
		way.setWayNodes(nodes);

		return way;
	}

	public void setWay(OverlayWay way) {		
		// invalidate local points
		this.points = new ArrayList<Point>();

		for(int i=0; i< way.getWayNodes()[0].length; i++) {
			Point p = new Point();
			GeoPoint g = way.getWayNodes()[0][i];

			p = projection.toPixels(g, null);

			this.points.add(p);
		}
	}

	public void addWayPoint(GeoPoint p) {			
		// using the stored projection transform from geoPoint toPixel
		this.points.add(projection.toPixels(p,null) );				
	}

	public void addWayPoint(Point p) {			
		// this is a screen pixel point already
		this.points.add(p);				
	}

	public void removeLastWayPoint() {
		// this is a handy function. it avoids having to get all the points and do it manually
		if(this.points.size() > 1) {
			this.points.remove(this.points.size()-1);
			this.requestRedraw();
		}
	}
	
	@Override
	protected void drawOverlayBitmap(Canvas canvas, Point drawPosition, Projection projection, byte drawZoomLevel) {
		this.draw(canvas,null,projection);		
	}

	private void draw(Canvas canvas, MapView mapv, Projection projection) {
		super.draw(canvas);						
		this.projection = projection;

		Path path = new Path();

		canvas.drawColor(0, Mode.CLEAR);

		Paint mPaint = defaultPaintText;
		int xPos = (canvas.getWidth() / 2);
		int yPos = (int) ((canvas.getHeight() / 10) - ((mPaint.descent() + mPaint.ascent()) / 2)) ; //((textPaint.descent() + textPaint.ascent()) / 2) is the distance from the baseline to the center.

		canvas.drawText("polygon draw mode", xPos , yPos, mPaint);

		if(points != null) {					

			if(points.size() >= 2) {

				// draw vertex and a polygon
				Iterator<Point> i = points.iterator();

				Point first = i.next();
				path.moveTo(first.x, first.y);				

				while(i.hasNext()){
					Point p = i.next();
					path.lineTo(p.x, p.y);					
				}

				//path.lineTo(first.x, first.y);
			}

			canvas.drawPath(path, defaultPaintOutline);
			
			// draw only the vertex
			for(Iterator<Point> i = points.iterator(); i.hasNext();){
				Point p = i.next();
				canvas.drawPoint(p.x, p.y, this.defaultPaintVertex);
			}
		}
	}

	public boolean handleTouchEvent(MotionEvent ev,MapView mapView) {
		boolean finished = false;		
		int[] v = new int[2];
		mapView.getLocationOnScreen(v);		
		int x = (int) ev.getX() - v[0];
		int y = (int) ev.getY() - v[1];
		int action = (ev.getAction() & MotionEvent.ACTION_MASK);				
		Point hit = new Point(x,y);

		switch (action)
		{	            			
		case MotionEvent.ACTION_MOVE:								
			// Update last point				
			if(this.points != null && !this.points.isEmpty() && selectedPoint < this.points.size()) {								
				this.points.set(selectedPoint, hit);
			}
			this.requestRedraw();											
			break;		
			// This is a rude implementation of on Tap event		
		case MotionEvent.ACTION_DOWN:
			startTime = System.currentTimeMillis();
			clickCount++;		

			selectedPoint = Integer.MAX_VALUE;
			if(this.points != null && !this.points.isEmpty()) {
				selectedPoint = checkPointHit(hit);
			}
			if(selectedPoint >= this.points.size() || selectedPoint < 0) {
				addWayPoint(hit);		
				selectedPoint = this.points.size() -1;
				this.requestRedraw();
			}

			break;
		case MotionEvent.ACTION_UP:
			long time = System.currentTimeMillis() - startTime;
			duration = duration + time;			
			if(clickCount >= 2)
			{
				if(duration <= MAX_DURATION)
				{					
					this.requestRedraw();
					// tell the caller that we're done here
					//TODO: i disable the double tap end unless we find a way to make it usable. 
					// Find this way, or in incoming revisions this will be deleted
					//finished =  true;  																     
				}
				clickCount = 0;
				duration = 0;     
			}						
			// check if we want to close it i.e a new point colliding with first
			if(this.points.size() > 2 && collide(hit,points.get(0)) && selectedPoint != 0) {
				hit = points.get(0);
				// TODO: confirmation dialog 
				finished = true;
			}
			this.points.set(selectedPoint, hit);		
			selectedPoint = Integer.MAX_VALUE;
			this.requestRedraw();

			break;	
		}		

		return finished;		
	}

	/*
	 * returns the index of the point matching hit coordinates or -1 if nothing is hit
	 */
	private int checkPointHit(Point hit) {
		int idx = 0;

		for(Iterator<Point> i = points.iterator(); i.hasNext(); idx++)
			if( collide(i.next(),hit) )
				return idx;
		return -1;
	}


	private boolean collide(Point p1, Point p2) {
		int thresh = (int) (initDefaultPaintVertex().getStrokeWidth() * 2);

		boolean res = false;

		int minx = Math.min(p1.x,p2.x);
		int maxx = Math.max(p1.x,p2.x);
		int miny = Math.min(p1.y,p2.y);
		int maxy = Math.max(p1.y,p2.y);

		if(maxx - minx <= thresh)
			if(maxy - miny <= thresh)
				res = true;
		return res;
	}


	public void clear() {
		this.points.clear();		
	}


	public List<Point> getPoints() {
		return points;
	}


	public void setPoints(List<Point> points) {
		this.points = points;
	}


	public Projection getProjection() {
		return projection;
	}


	public void setProjection(Projection projection) {
		this.projection = projection;
	}

	public Paint getDefaultPaintFill() {
		return defaultPaintFill;
	}

	public void setDefaultPaintFill(Paint defaultPaintFill) {
		this.defaultPaintFill = defaultPaintFill;
	}

	public Paint getDefaultPaintOutline() {
		return defaultPaintOutline;
	}

	public void setDefaultPaintOutline(Paint defaultPaintOutline) {
		this.defaultPaintOutline = defaultPaintOutline;
	}

	public Paint getDefaultPaintVertex() {
		return defaultPaintVertex;
	}

	public void setDefaultPaintVertex(Paint defaultPaintVertex) {
		this.defaultPaintVertex = defaultPaintVertex;
	}

	public Paint getDefaultPaintText() {
		return defaultPaintText;
	}

	public void setDefaultPaintText(Paint defaultPaintText) {
		this.defaultPaintText = defaultPaintText;
	}

}
